package com.hzw.saas.web.im.im.websocket;

import com.hzw.saas.api.chat.IChatEventService;
import com.hzw.saas.api.chat.bo.ChatEventBO;
import com.hzw.saas.common.security.token.SaasTokenService;
import com.hzw.saas.common.security.utils.SecurityUtils;
import com.hzw.saas.web.im.im.ImContext;
import com.hzw.saas.web.im.im.SaasImServer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author sonam
 * @sine 2021/10/21 9:54 上午
 */
@Slf4j
@Sharable
@Component("httpRequestHandler")
public class HttpRequestHandler extends SimpleChannelInboundHandler<Object> {
    @Resource
    private SaasTokenService userTokenServices;
    @Resource
    private IChatEventService chatEventService;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            ctx.fireChannelRead(((WebSocketFrame) msg).retain());
        }
    }

    /**
     * 处理Http请求，主要是完成HTTP协议到Websocket协议的升级
     * @param ctx
     * @param req
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        if (!req.decoderResult().isSuccess()) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        String uri = req.uri();
        log.info("uri: {}", uri);

        if(!uri.startsWith(SaasImServer.PATH)) {
            sendHttpResponse(ctx, req,
                new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN));
            return;
        }

        int index = uri.indexOf("token=");
        if(index == -1) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED));
            return;
        }
        String token = uri.substring(index + 6).split("&")[0];
        if(!handlerToken(token)) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED));
            return;
        }

        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                "ws:/" + ctx.channel() + "/websocket", null, false);
        WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
        ImContext.webSocketHandShakerMap.put(ctx.channel().id().asLongText(), handshaker);

        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            // 上线事件
            ChatEventBO chatEventBO = new ChatEventBO();
            String userId = SecurityUtils.getUserId();
            chatEventBO.setSourceId(userId);
            chatEventBO.setEventDetail(userId + "up");
            chatEventBO.setEventType(11);
            chatEventService.addEvent(chatEventBO);
            ImContext.userChannel.put(userId, ctx.channel());
            handshaker.handshake(ctx.channel(), req);
        }
    }

    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        // 如果是非Keep-Alive，关闭连接
        boolean keepAlive = HttpUtil.isKeepAlive(req);
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!keepAlive) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 异常处理，关闭channel
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    private boolean handlerToken(String token) {
        try {
            OAuth2Authentication auth2Authentication = userTokenServices.loadAuthentication(token.replace("bearer", ""));
            SecurityContextHolder.getContext().setAuthentication(auth2Authentication);
            return true;
        } catch (Exception e) {
            log.error("token无效", e);
            return false;
        }
    }
}
